/// <reference path="./Maths.ts" />
/// <reference path="./Pool.ts" />

namespace apeng {
    let _x: number = 0.0
    let _y: number = 0.0

    export interface IVector2 {
        x: number
        y: number
    }
    export type TVectorSet = number | number[] | IVector2

    export class Vector2 implements IVector2 {

        public static readonly pool = new PoolOnce(1000, () => new Vector2(), (data) => { data.set() })

        /**零向量,禁止修改*/
        public static readonly ZERO: Vector2 = new Vector2(0.0, 0.0)
        /**一向量,禁止修改*/
        public static readonly ONE: Vector2 = new Vector2(1.0, 1.0)
        /**一向量,禁止修改*/
        public static readonly UP: Vector2 = new Vector2(0, 1)
        /**一向量,禁止修改*/
        public static readonly RIGHT: Vector2 = new Vector2(1, 0)
        /**一向量,禁止修改*/
        public static readonly LEFT: Vector2 = new Vector2(-1, 0)
        /**一向量,禁止修改*/
        public static readonly DOWN: Vector2 = new Vector2(0, -1)

        /**逐元素向量最小值 */
        public static min(out: IVector2, a: IVector2, b: IVector2) {
            out.x = Math.min(a.x, b.x)
            out.y = Math.min(a.y, b.y)
            return out
        }

        /**逐元素向量最大值 */
        public static max(out: IVector2, a: IVector2, b: IVector2) {
            out.x = Math.max(a.x, b.x)
            out.y = Math.max(a.y, b.y)
            return out
        }

        /**两个向量的夹角角度 */
        public static twoAngle(a: IVector2, b: IVector2, orgin: IVector2 = Vector2.ZERO) {
            let x1 = a.x - orgin.x
            let y1 = a.y - orgin.y

            let x2 = b.x - orgin.x
            let y2 = b.y - orgin.y

            // 余弦定理
            //            C(0,0)
            //           /θ\
            //         b/   \a
            //         /     \
            // (x1,y1)A_______B(x2,y2)
            //            c
            // c^2 = a^2 + b^2 - 2*a*b*cosθ

            let cosRadian =
                (Math.pow(x1, 2) +
                    Math.pow(y1, 2) +
                    Math.pow(x2, 2) +
                    Math.pow(y2, 2) -
                    (Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))) /
                (2 *
                    Math.sqrt(Math.pow(x1, 2) + Math.pow(y1, 2)) *
                    Math.sqrt(Math.pow(x2, 2) + Math.pow(y2, 2)))

            return Maths.toDegree(Math.acos(cosRadian))
        }

        public static sort(points: IVector2[]): IVector2[] {
            v2T.set()
            for (let point of points)
                v2T.addSelf(point)
            let size = points.length
            v2T.x /= size
            v2T.y /= size

            for (let i = 0; i < size - 1; i++) {
                for (let j = 0; j < size - 1 - i; j++) {
                    if (this.sortItem(points[j], points[j + 1], v2T)) {
                        let temp = points[j]
                        points[j] = points[j + 1]
                        points[j + 1] = temp
                    }
                }
            }

            return points
        }

        private static sortItem(a: IVector2, b: IVector2, center: IVector2) {
            v2T2.set(a).subSelf(center)
            v2T3.set(b).subSelf(center)
            let dot = this.dot(v2T2, v2T3)
            if (dot > 0)
                return false
            return true
        }

        public static distance(value1: IVector2, value2: IVector2): number {
            _x = value1.x - value2.x
            _y = value1.y - value2.y
            return Math.sqrt(_x * _x + _y * _y)
        }

        /**
        * 两个三维向量距离。 不开方
        * @param	value1 向量1。
        * @param	value2 向量2。
        * @return	距离。
        */
        public static distanceSqr(value1: IVector2, value2: IVector2): number {
            _x = value1.x - value2.x
            _y = value1.y - value2.y
            return _x * _x + _y * _y
        }

        /**
       * 缩放当前向量。如果你想结果保存到另一个向量，可使用 mul() 代替。
       * @method mulSelf
       * @param {number} num
       * @return {Vec2} returns this
       * @chainable
       * @example
       * v.mulSelf(5);// return Vec2 {x: 50, y: 50};
       */
        public static mul(out: IVector2, value: IVector2, num: number | IVector2): IVector2 {
            Vector2.set(out, value)
            if (typeof num == "number") {
                out.x *= num
                out.y *= num
            }
            else {
                out.x *= num.x
                out.y *= num.y
            }
            return out
        }

        public static equals(a: IVector2, b: IVector2, offset = 0.01) {
            if (Math.abs(a.x - b.x) <= offset && Math.abs(a.y - b.y) <= offset)
                return true
            return false
        }

        public static equals2f(value1: IVector2, x: number, y: number) {
            return value1.x === x && value1.y === y
        }

        /**
       * 以自身为原点 获取 另外点同自身的角度 0~360
       * @param other
       * @param rotate 0~360 默认 0度=>正右方； 逆时针旋转角度
       */
        public static angle360(self: IVector2, other: IVector2, rotate: number = 0): number {
            // 获取到角度
            let sub = v2T.set(other).subSelf(self) as Vector2
            return Vector2.angle(sub, rotate)
        }

        public static angle(value: IVector2, rotate: number = 0): number {
            // 改成逆时针方向
            if (rotate != 0) {
                rotate = 360 - rotate
                Vector2.rotate(value, value, Maths.toRadian(rotate))
            }

            let angle = Maths.toDegree(Math.atan2(value.y, value.x))

            // 转换成 0 ~ 360
            if (angle < 0) angle = 360 - Math.abs(angle)

            return angle
        }

        /**
         * 缩放二维向量。
         * @param	a 源二维向量。
         * @param	b 缩放值。
         * @param	out 输出二维向量。
         */
        public static scale(a: Vector2, b: number, out: Vector2): void {
            out.x = a.x * b
            out.y = a.y * b
        }

        /**
        * 求两个二维向量的点积。
        * @param	a left向量。
        * @param	b right向量。
        * @return   点积。
        */
        public static dot(a: IVector2, b: IVector2): number {
            return (a.x * b.y) - (a.y * b.x)
        }

        /**
         * 归一化二维向量。
         * @param	s 源三维向量。
         * @param	out 输出三维向量。
         */
        public static normalize(out: IVector2, value: IVector2): IVector2 {
            var x: number = value.x, y: number = value.y
            var len: number = x * x + y * y
            if (len > 0) {
                len = 1 / Math.sqrt(len)
                out.x = x * len
                out.y = y * len
            }
            return out
        }

        /**
         * @zh 根据指定的插值比率，从当前向量到目标向量之间做插值。
         * @param to 目标向量。
         * @param ratio 插值比率，范围为 [0,1]。
         */
        public static lerp(out: IVector2, from: IVector2, to: IVector2, ratio: number) {
            _x = from.x
            _y = from.y
            out.x = _x + ratio * (to.x - _x)
            out.y = _y + ratio * (to.y - _y)
            return out
        }

        /**
          * 转换为方便阅读的字符串。
          * @method toString
          * @return {string}
          */
        public static toString(value: Vector2): string {
            return "(" + value.x.toFixed(2) + ", " + value.y.toFixed(2) + ")"
        }

        /**
         * @zh 将当前向量的旋转
         * @param radians 旋转角度（弧度制）。
         */
        public static rotate(out: IVector2, value: IVector2, radians: number): IVector2 {

            _x = value.x
            _y = value.y

            const sin = Math.sin(radians)
            const cos = Math.cos(radians)
            out.x = cos * _x - sin * _y
            out.y = sin * _x + cos * _y
            return out
        }

        /**
         * 向量减法，并返回新结果
         * @param vector
         * @param out
         */
        public static sub<T extends IVector2>(out: T, a: IVector2, b: IVector2): T {
            out.x = a.x - b.x
            out.y = a.y - b.y
            return out
        }


        /**
         * 求向量长度
         */
        public static len(value: IVector2): number {
            return Math.sqrt(Vector2.lengthSqr(value))
        }

        /**
         * 求向量长度平方
         */
        public static lengthSqr(value: IVector2): number {
            return value.x * value.x + value.y * value.y
        }


        /**
         * 向量减法，并返回新结果
         * @param vector
         * @param out
         */
        public static add(out: IVector2, a: IVector2, b: IVector2): IVector2 {
            out.x = a.x + b.x
            out.y = a.y + b.y
            return out
        }

        /**两个向量的中心点 */
        public static center(out: IVector2, a: IVector2, b: IVector2): IVector2 {
            return Vector2.lerp(out, a, b, .5)
        }

        public static clampf(out: IVector2, value: IVector2, min: IVector2, max: IVector2) {
            out.x = Maths.clampf(value.x, min.x, max.x)
            out.y = Maths.clampf(value.y, min.y, max.y)
            return out
        }

        public static set<T extends IVector2>(self: T, x?: TVectorSet, y?: number): T {
            if (Array.isArray(x)) {
                self.x = x[0]
                self.y = x[1]
            }
            else if (x && typeof x === "object") {
                self.x = x.x
                self.y = x.y
            } else {
                self.x = (x || 0) as number
                self.y = y || 0
            }
            return self as T
        }

        /**
         * 找到多边形的中心点
         * @param polygon 
         */
        public static polygonByCenterPoint(out: IVector2, polygon: IVector2[]): IVector2 {
            let minX = polygon[0].x
            let maxX = polygon[0].x
            let minY = polygon[0].y
            let maxY = polygon[0].y
            for (let value of polygon) {
                if (minX > value.x)
                    minX = value.x
                if (maxX < value.x)
                    maxX = value.x
                if (minY > value.y)
                    minY = value.y
                if (maxY < value.y)
                    maxY = value.y
            }
            out.x = minX + Math.abs(maxX - minX) / 2
            out.y = minY + Math.abs(maxY - minY) / 2
            return out
        }


        /**
         * 近似判断两个点是否相等。<br/>
         * 判断 2 个向量是否在指定数值的范围之内，如果在则返回 true，反之则返回 false。
         * @method fuzzyEquals
         * @param {Vec2s} other
         * @param {Number} variance
         * @return {Boolean}
         */
        public static fuzzyEquals(self: IVector2, other: IVector2, variance: number): boolean {
            if (self.x - variance <= other.x && other.x <= self.x + variance) {
                if (self.y - variance <= other.y && other.y <= self.y + variance) return true
            }
            return false
        }


        /**X轴坐标*/
        public x: number = 0
        /**Y轴坐标*/
        public y: number = 0

        /**
         * 创建一个 <code>Vector2</code> 实例。
         * @param	x  X轴坐标。
         * @param	y  Y轴坐标。
         */
        constructor(x?: TVectorSet, y?: number) {
            this.set(x, y)
        }

        public mul(out: IVector2, num: number | IVector2) { return Vector2.mul(out, this, num) }
        public mulSelf(num: number | IVector2): Vector2 { return Vector2.mul(this, this, num) as Vector2 }
        public len(): number { return Vector2.len(this) }
        public lengthSqr(): number { return Vector2.lengthSqr(this) }
        public equals(other: IVector2): boolean { return Vector2.equals(this, other) }
        public equals2f(x: number, y: number): boolean { return Vector2.equals2f(this, x, y) }
        public set(x?: TVectorSet, y?: number): Vector2 { return Vector2.set(this, x, y) }
        public sub(out: IVector2, value: IVector2): IVector2 { return Vector2.sub(out, this, value) }
        public subSelf(value: IVector2): Vector2 { return Vector2.sub(this, this, value) as Vector2 }
        public rotate(out: IVector2, radians: number): IVector2 { return Vector2.rotate(out, this, radians) }
        public rotateSelf(radians: number): Vector2 { return Vector2.rotate(this, this, radians) as Vector2 }
        public normalize(out: IVector2): Vector2 { return Vector2.normalize(out, this) as Vector2 }
        public normalizeSelf(): Vector2 { return Vector2.normalize(this, this) as Vector2 }
        public add(out: IVector2, value: IVector2): IVector2 { return Vector2.add(out, this, value) }
        public addSelf(value: IVector2): Vector2 { return Vector2.add(this, this, value) as Vector2 }
        public toString() { return Vector2.toString(this) }
        public clampf(out: IVector2, min: IVector2, max: IVector2) { return Vector2.clampf(out, this, min, max) as Vector2 }
        public clampfSelf(min: IVector2, max: IVector2) { return Vector2.clampf(this, this, min, max) as Vector2 }

    }

    const v2T = new Vector2(1.0, 1.0)
    const v2T2 = new Vector2(1.0, 1.0)
    const v2T3 = new Vector2(1.0, 1.0)
}
